﻿using System;
using System.Linq;

namespace RayDen.Library.Core.Primitives
{
    using System.ComponentModel;
    using System.Runtime.InteropServices;

    [TypeConverter(typeof(ExpandableObjectConverter))]
    [Serializable]
    [StructLayout(LayoutKind.Sequential)]
    public struct Point : IEquatable<Point>,ISerializableValue
    {
        public static float Epsilon = 0.000001f;
        public static Point Zero = new Point(0f);
        public float x, y, z;


        public float X
        {
            get { return x; }
            set { x = value; }
        }

        public float Y
        {
            get { return y; }
            set { y = value; }
        }

        public float Z
        {
            get { return z; }
            set { z = value; }
        }
        public static unsafe readonly int SizeInBytes = sizeof(Point);
        public float this[int index]
        {
            get
            {
                switch (index)
                {
                    case 0:
                        return x;
                    case 1:
                        return y;
                    case 2:
                        return z;
                    default:
                        throw new ArgumentException("index");
                }
            }

            set
            {
                switch (index)
                {
                    case 0:
                        x = value;
                        break;
                    case 1:
                        y = value;
                        break;
                    case 2:
                        z = value;
                        break;
                    default:
                        throw new ArgumentException("index");
                }
            }

        }

        public float Component(Axis p)
        {
            switch (p)
            {
                case Axis.PLANE_X:
                    return x;
                case Axis.PLANE_Y:
                    return y;
                case Axis.PLANE_Z:
                    return z;
                default:
                    throw new ArgumentException("index");
            }
        }


        public Point(float a)
        {
            x = y = z = a;
            //     w = 0;
        }


        public Point(float a, float b, float c)
        {
            x = a;
            y = b;
            z = c;
#if DEBUG
            RayDen.Library.Core.Components.Assert.IsNotNaN(x);
            RayDen.Library.Core.Components.Assert.IsNotNaN(y);
            RayDen.Library.Core.Components.Assert.IsNotNaN(z);
#endif
            // w = 0;
        }

        public Point(ref Point v)
        {
            x = v.x;
            y = v.y;
            z = v.z;
#if DEBUG
            RayDen.Library.Core.Components.Assert.IsNotNaN(x);
            RayDen.Library.Core.Components.Assert.IsNotNaN(y);
            RayDen.Library.Core.Components.Assert.IsNotNaN(z);
#endif
            //   w = 0;
        }
        public Point(float[] val)
        {
            x = val[0];
            y = val[1];
            z = val[2];
#if DEBUG
            RayDen.Library.Core.Components.Assert.IsNotNaN(x);
            RayDen.Library.Core.Components.Assert.IsNotNaN(y);
            RayDen.Library.Core.Components.Assert.IsNotNaN(z);
#endif
            // w = 0;
        }
        public Point(Point v)
        {
            x = v.x;
            y = v.y;
            z = v.z;
#if DEBUG
            RayDen.Library.Core.Components.Assert.IsNotNaN(x);
            RayDen.Library.Core.Components.Assert.IsNotNaN(y);
            RayDen.Library.Core.Components.Assert.IsNotNaN(z);
#endif
            // w = 0;
        }
        public Point(Vector v)
        {
            x = v.x;
            y = v.y;
            z = v.z;
#if DEBUG
            RayDen.Library.Core.Components.Assert.IsNotNaN(x);
            RayDen.Library.Core.Components.Assert.IsNotNaN(y);
            RayDen.Library.Core.Components.Assert.IsNotNaN(z);
#endif
            // w = 0;
        }


        public bool Equals(Point other)
        {
            float dx = Math.Abs(other.x - x);
            float dy = Math.Abs(other.y - y);
            float dz = Math.Abs(other.z - z);

            return (dx < Epsilon) && (dy < Epsilon) && (dz < Epsilon);
        }

        public static explicit operator Vector(Point n)
        {
            return new Vector(n.x, n.y, n.z);
        }
        public static explicit operator Point(Vector n)
        {
            return new Point(n.x, n.y, n.z);
        }

        public static Point operator +(Point v1, Vector v2)
        {
            return Point.Add(ref v1, ref v2);
        }
        public static Point operator +(Point v1, Point v2)
        {
            return Point.Add(ref v1, ref v2);
        }
        public static Vector operator -(Point v1, Point v2)
        {
            Vector v;
            Point.Sub(ref v1, ref v2, out v);
            return v;
        }
        public static Vector operator -(Point v1, Vector v2)
        {
            Vector v;
            Point.Sub(ref v1, ref v2, out v);
            return v;
        }

        public static Point operator -(Point v1, float v2)
        {
            return new Point(v1.x - v2, v1.y - v2, v1.z - v2);
        }
        public static Point operator +(Point v1, float v2)
        {
            return new Point(v1.x + v2, v1.y + v2, v1.z + v2);
        }

        public static Point operator *(Point v, float f)
        {
            return new Point(v.x * f, v.y * f, v.z * f);
        }

        public static Point operator *(float f, Point v)
        {
            return new Point(v.x * f, v.y * f, v.z * f);
        }

        public static Point operator /(Point v, float f)
        {
            float divider = 1.0f / f;
            return new Point(v.x * divider, v.y * divider, v.z * divider);
        }

        public static Point operator /(float f, Point v)
        {
            return new Point(f / v.x, f / v.y, f / v.z);
        }
        public static Point operator -(Point v)
        {
            return new Point(-v.x, -v.y, -v.z);
        }
        public static Point Add(ref Point a, ref Vector b)
        {
            return new Point(a.x + b.x, a.y + b.y, a.z + b.z);
        }
        public static Point Add(ref Point a, ref Point b)
        {
            return new Point(a.x + b.x, a.y + b.y, a.z + b.z);
        }
        public static void Sub(ref Point a, ref Point b, out Vector v)
        {
            v = new Vector(a.x - b.x, a.y - b.y, a.z - b.z);
        }
        public static void Sub(ref Point a, ref Vector b, out Vector v)
        {
            v = new Vector(a.x - b.x, a.y - b.y, a.z - b.z);
        }
        public static void Sub(ref Vector a, ref  Point b, out Point v)
        {
            v = new Point(a.x - b.x, a.y - b.y, a.z - b.z);
        }
        public float Length
        {
            get
            {
                return MathLab.Sqrt(x * x + y * y + z * z);
            }
        }

        public float Length2
        {
            get { return x * x + y * y + z * z; }
        }

        public Point Normalize()
        {
            float l = 1f / MathLab.Sqrt(x * x + y * y + z * z);
            //InvSqrt(x * x + y * y + z * z);
            x *= l;
            y *= l;
            z *= l;

            return this;
        }
        public static float Dot(ref Point a, ref Point b)
        {
            return (a.x * b.x + a.y * b.y + a.z * b.z);
        }

        public static float AbsDot(ref Point a, ref Point b)
        {
            return Math.Abs(a.x * b.x + a.y * b.y + a.z * b.z);
        }

        public static Vector Cross(ref Vector v1, ref Point v2)
        {
            return new Vector(
                  (v1.y * v2.z) - (v1.z * v2.y),
                  (v1.z * v2.x) - (v1.x * v2.z),
                  (v1.x * v2.y) - (v1.y * v2.x));
        }

        public static Vector Cross(ref Point v1, ref  Vector v2)
        {
            return new Vector(
                  (v1.y * v2.z) - (v1.z * v2.y),
                  (v1.z * v2.x) - (v1.x * v2.z),
                  (v1.x * v2.y) - (v1.y * v2.x));
        }

        public static float Dot(ref Point n1, ref Vector v2)
        {
            return n1.x * v2.x + n1.y * v2.y + n1.z * v2.z;
        }
        public static float Dot(ref Vector v2, ref Point n1)
        {
            return n1.x * v2.x + n1.y * v2.y + n1.z * v2.z;
        }

        public static float AbsDot(ref Point a, ref Vector b)
        {
            return Math.Abs(a.x * b.x + a.y * b.y + a.z * b.z);
        }

        public static float AbsDot(ref Vector a, ref  Point b)
        {
            return Math.Abs(a.x * b.x + a.y * b.y + a.z * b.z);
        }

        public bool IsZero()
        {
            return (x == 0.0) && (y == 0.0) && (z == 0.0);
        }
        public float[] ToArray()
        {
            return new float[] { x, y, z };
        }

        public override string ToString()
        {
            return string.Format("{0:F4} {1:F4} {2:F4}", x, y, z);
        }

        //Hush on you
        public Vector ToVec()
        {
            return new Vector(x, y, z);
        }

        public void SetComponent(byte axis, float value)
        {
            switch (axis)
            {
                case 0:
                    x = value;
                    break;
                case 1:
                    y = value;
                    break;
                case 2:
                    z = value;
                    break;
                default:
                    throw new ArgumentException("index");
            }
        }
        public byte[] Serialize()
        {
            return new[]
                {
                    BitConverter.GetBytes(x),
                    BitConverter.GetBytes(y),
                    BitConverter.GetBytes(z)
                }.SelectMany(t => t).ToArray();
        }

        public void Deserialize(byte[] data, int offset = 0)
        {
            x = BitConverter.ToSingle(data, 0 + offset);
            y = BitConverter.ToSingle(data, 4 + offset);
            z = BitConverter.ToSingle(data, 8 + offset);
        }
    }
}